package simpleftp.server;

import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.Void;
import java.util.concurrent.Callable;
import javax.swing.tree.DefaultMutableTreeNode;

import simpleftp.client.control.Command;


/**
 * ServeurFTP is a simple package that implements a Java FTP server. With
 * ServeurFTP, you can connect to an FTP client and download multiple files. 
 */
public class FTPServer implements Callable<Void> 
{
	private static boolean DEBUG = false;
	private String response, user;
	private int validations;
	private File f;
	private StringBuilder sb;
	private int portnum1, portnum2;
	private Properties p;
	private ServerSocket dl;
	private Socket dl2;
	private FileInputStream fis;
	private Socket socket = null;
	private BufferedReader reader = null;
	private BufferedWriter writer = null;
	private ObjectOutputStream output = null;
	
	
	
	
	public FTPServer(Socket s) {
		socket = s;
		validations = 0;
		try {
			p = new Properties();
			fis = new FileInputStream("users.properties");
			p.load(fis);
			try {
				reader = new BufferedReader(new InputStreamReader(
						socket.getInputStream()));
				writer = new BufferedWriter(new OutputStreamWriter(
						socket.getOutputStream()));
				output = new ObjectOutputStream(socket.getOutputStream());
				
				sendLine("220 ");
				validations++;
				f = new File(System.getProperty("user.dir"));
				System.out.println("FTP Server : new client from " + socket.getInetAddress().getHostAddress());
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			}
		} catch (IOException ioex) {
			System.out.println("Error: Properties File didn't load");
			try {
				sendLine("error ");
			} catch (IOException e) {
				e.printStackTrace();
			}
			ioex.printStackTrace();
		}
		
	}

	
	public Void call() throws Exception {
		try {
			response = readLine();
			if (response.startsWith("USER")) {
				if (p.containsKey(response.substring(5))) {
					user = response.substring(5);
					System.out.println("Server knows user " + response);
					sendLine("331 ");
					validations++;
				} else {
					System.out.println("Server does no know user " + response);
					sendLine("error ");
				}
			}
			response = readLine();
			if (response.startsWith("PASS")) {
				if (p.get((String) user).equals(response.substring(5))) {
					System.out.println("Server validates pass " + response);
					sendLine("230 ");
					validations++;
				} else {
					sendLine("error ");
				}
			}
			// to get the home path
			response = readLine();
			if (response.startsWith("HOME")) 
			{
				sendLine("1000 "+System.getProperty("user.home")+
						" "+System.getProperty("file.separator"));
			} 
			else 
			{
				sendLine("error ");
			}
		}

		catch (IOException e) {
			// TODO Auto-generated catch block
			try {
				sendLine("error ");
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}

		while (validations == 3) {
			try {
				response = readLine();

				
				switch (Command.valueOf(response.split(" ")[0])) {

				case PWD:
					sendLine("257 " + System.getProperty("user.dir"));
					break;
				
				case CDUP:
					String curDir = System.getProperty("user.dir");
					String[] temp = curDir.split(System.getProperty("file.separator").toString());
					StringBuilder sb2 = new StringBuilder();
					sb2.append("/");
					// we go until length -1 to remove the last directory
					// we start from 1 to remove the first blank
					for(int i=1;i<temp.length-1;i++)
					{
						sb2.append(temp[i]);
						sb2.append(System.getProperty("file.separator").toString());
					}
					// we remove the last "/"
					sb2.deleteCharAt(sb2.length()-1);
					// we set the new current directory
					System.setProperty("user.dir", sb2.toString());
					System.out.println("new current cirectory : "+System.getProperty("user.dir"));
					f = new File(System.getProperty("user.dir"));
					
					// we explicitly remove the break to continue 
					// on the list case to return the new tree
					//break;

				case LIST:
					sb = new StringBuilder("200 ");
					String[] fileNames = f.list();
					for (String fileName : fileNames) {
						sb = sb.append(fileName + "|");
					}
					sendLine(sb.toString());
					
					sendObjects(f);
					break;

				case CWD:
					System.out.println("CWD "+response.substring(4));
					System.setProperty("user.dir", 
							System.getProperty("user.dir").toString()+
						    System.getProperty("file.separator").toString()
						    +response.substring(4));
					
					System.out.println("CWD new current directory:"
							+ System.getProperty("user.dir").toString());
					f = new File(System.getProperty("user.dir"));
					sendLine("250 ");
					sendObjects(f);
					break;

				case PASV:
					pasv();
					break;

				case STOR:
					stor();
					break;
				
				case RETR:
					retr();
					break;

				case QUIT:
					socket.close();
					System.out.println("QUIT :Goodbye");
					validations = 0;
					break;

				default:
					System.out.println("Unknown Command.");
					sendLine("error");
					break;

				}
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {

					e1.printStackTrace();
				}
				e.printStackTrace();
			}

		}
		Void Void = null;
		return Void;
	}
	
	public boolean retr(File file) throws IOException 
    {
        if (file.isDirectory()) {
            throw new IOException("SimpleFTP cannot upload a directory.");
        }        
        String filename = file.getName();
        return retr (new FileInputStream(file), filename);
    }
    
    
    /**
     * Sends a file to be stored on the FTP server.
     * Returns true if the file transfer was successful.
     * The file is sent in passive mode to avoid NAT or firewall problems
     * at the client end.
     */
    public  boolean retr(InputStream inputStream, String filename) throws IOException {

        BufferedInputStream input = new BufferedInputStream(inputStream);
        
        sendLine("PASV");
        String response = readLine();
        if (!response.startsWith("227 ")) {
            throw new IOException("SimpleFTP could not request passive mode: " + response);
        }
        // get the connection parameter of the server
        String ip = null;
        int port = -1;
        int opening = response.indexOf('(');
        int closing = response.indexOf(')', opening + 1);
        if (closing > 0) {
            String dataLink = response.substring(opening + 1, closing);
            StringTokenizer tokenizer = new StringTokenizer(dataLink, ",");
            try {
                ip = tokenizer.nextToken() + "." + tokenizer.nextToken() + "." + tokenizer.nextToken() + "." + tokenizer.nextToken();
                port = Integer.parseInt(tokenizer.nextToken()) * 256 + Integer.parseInt(tokenizer.nextToken());
            }
            catch (Exception e) {
                throw new IOException("SimpleFTP received bad data link information: " + response);
            }
        }
        
        // start sending data
        sendLine("STOR " + filename);
        
        Socket dataSocket = new Socket(ip, port);
        
        response = readLine();
        if (!response.startsWith("150 ")) {
            throw new IOException("SimpleFTP was not allowed to send the file: " + response);
        }
        
        BufferedOutputStream output = new BufferedOutputStream(dataSocket.getOutputStream());
        byte[] buffer = new byte[4096];
        int bytesRead = 0;
        while ((bytesRead = input.read(buffer)) != -1) {
            output.write(buffer, 0, bytesRead);
        }
        output.flush();
        output.close();
        input.close();
        
        response = readLine();
        return response.startsWith("226 ");
    }


	private void sendObjects(File f2) throws Exception 
	{
		if(socket==null)
		{
			throw new IOException("FTP server is not connected");
		}
		DefaultMutableTreeNode userRoot = new DefaultMutableTreeNode(f2.getName());
		
		populateTree(userRoot,f2);
		output.writeObject(userRoot);
	}

	private void populateTree(DefaultMutableTreeNode userRoot, File root) {
		File[] files = root.listFiles();
		for (File f: files)
		{
			DefaultMutableTreeNode child = new DefaultMutableTreeNode(f.getName());
			userRoot.add(child);
			if(f.isDirectory())
			{
				populateTree(child,f);
			}	
		}
	}

	/**
     * Sends a raw command to the FTP server.
     */

	private void sendLine(String line) throws IOException {
		if (socket == null) {
			throw new IOException("FTPServer is not connected.");
		}
		try {
			writer.write(line + "\r\n");
			writer.flush();
			if (DEBUG) {
				System.out.println("> " + line);
			}
		} catch (IOException e) {
			socket = null;
			throw e;
		}
	}

	private String readLine() throws IOException {
		String line = reader.readLine();
		if (DEBUG) {
			System.out.println("< " + line);
		}
		return line;
	}

	private void stor() {
		try {
			dl2 = dl.accept();
			sendLine("150 ");
			OutputStream os = new FileOutputStream(new File(
					System.getProperty("user.dir")
							+ System.getProperty("file.separator")
							+ response.substring(5)));
			InputStream is = dl2.getInputStream();
			byte[] buffer = new byte[4096];
			int bytesRead = 0;
			while ((bytesRead = is.read(buffer)) != -1) {
				os.write(buffer, 0, bytesRead);
			}
			os.flush();
			os.close();
			is.close();
			sendLine("226 ");
			dl.close();
			dl2.close();
			System.out.println("STOR " + response.split(" ")[1]
					+ " : File received");
		} catch (IOException e) {
			try {
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}
	
	private void retr() {
		try {
			dl2 = dl.accept();
			BufferedInputStream input = 
					new BufferedInputStream(
							new FileInputStream(
										new File( System.getProperty("user.dir")
												+ System.getProperty("file.separator")
												+ response.substring(5)
											    )
											   )
					     					);
			
			BufferedOutputStream output = new BufferedOutputStream(dl2.getOutputStream());
	        byte[] buffer = new byte[4096];
	        int bytesRead = 0;
	        while ((bytesRead = input.read(buffer)) != -1) {
	            output.write(buffer, 0, bytesRead);
	        }
	        output.flush();
	        output.close();
	        input.close();
	        dl.close();
			dl2.close();
			System.out.println("RETR " + response.split(" ")[1]
					+ " : File Sent");
		} catch (IOException e) {
			try {
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}

	private void pasv() {
		try {
			dl = new ServerSocket(0);
			portnum1 = dl.getLocalPort() / 256;
			portnum2 = dl.getLocalPort() % 256;
			sendLine("227 ("
					+ InetAddress.getLocalHost().getHostAddress()
							.replace('.', ',') + "," + portnum1 + ","
					+ portnum2 + ")");
			System.out.println("PASV success");
		} catch (IOException e) {
			try {
				sendLine("error ");
				System.out.println("pasv error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}
	
	public static void setDebug(boolean b) {
		DEBUG = b;
	}
}
